﻿using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Abp.Application.Services;
using Abp.Application.Services.Dto;
using Abp.Authorization;
using Abp.Collections.Extensions;
using Abp.Domain.Repositories;
using Abp.Extensions;
using Abp.IdentityFramework;
using Abp.Linq.Extensions;
using Abp.Zero.Configuration;
using pandx.Mulan.Authorization;
using pandx.Mulan.Authorization.Roles;
using pandx.Mulan.Authorization.Users;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using pandx.Mulan.Authorization.Roles.Dto;
using System.Linq.Dynamic.Core;
using Abp.Threading;
using Abp.UI;
using Castle.DynamicProxy.Internal;
using Microsoft.AspNetCore.Components.Forms;
using pandx.Mulan.Authorization.Permissions;
using pandx.Mulan.Authorization.Permissions.Dto;
using pandx.Mulan.Authorization.Users.Dto;
using pandx.Mulan.Dto;

namespace pandx.Mulan.Authorization.Roles
{
    public class RoleAppService : MulanAppServiceBase, IRoleAppService
    {
        private readonly IRoleManagementConfig _roleManagementConfig;

        public RoleAppService(IRoleManagementConfig roleManagementConfig
        )
        {
            _roleManagementConfig = roleManagementConfig;
        }

        [AbpAuthorize(AppPermissions.Pages_Administration_Roles_Browse)]
        public async Task<ListResultDto<RoleListDto>> GetAllRoles()
        {
            var query = RoleManager.Roles;
            var roles = await query.OrderBy(r => r.DisplayName).ToListAsync();

            return new ListResultDto<RoleListDto>(ObjectMapper.Map<List<RoleListDto>>(roles));
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Roles_Browse)]
        public async Task<PagedResultDto<RoleListDto>> ObtainRoles(ObtainRolesInput input)
        {
            if (input.IsStatic == null || input.IsStatic?.Count == 0)
            {
                input.IsStatic = new List<bool> { true, false };
            }

            if (input.IsDefault == null || input.IsDefault?.Count == 0)
            {
                input.IsDefault = new List<bool> { true, false };
            }

            var query = RoleManager.Roles
                .Where(r => r.CreationTime >= input.StartTime && r.CreationTime <= input.EndTime)
                .Where(r => input.IsStatic.Contains(r.IsStatic))
                .Where(r => input.IsDefault.Contains(r.IsDefault))
                .WhereIf(!input.Filter.IsNullOrWhiteSpace(),
                    r => r.DisplayName.Contains(input.Filter) ||
                         r.Name.Contains(input.Filter) ||
                         r.Description.Contains(input.Filter));
            if (input.Permissions != null && input.Permissions.Any(p => !string.IsNullOrEmpty(p)))
            {
                input.Permissions = input.Permissions.Where(p => !string.IsNullOrEmpty(p)).ToList();
                var staticRoleName = _roleManagementConfig.StaticRoles.Where(r =>
                        r.GrantAllPermissionsByDefault && r.Side == AbpSession.MultiTenancySide)
                    .Select(r => r.RoleName).ToList();
                foreach (var permission in input.Permissions)
                {
                    query = query.Where(r => r.Permissions.Any(rp => rp.Name == permission)
                        ? r.Permissions.Any(rp => rp.Name == permission && rp.IsGranted)
                        : staticRoleName.Contains(r.Name));
                }
            }


            var roleCount = await query.CountAsync();
            var roles = await query.OrderBy(input.Sorting).PageBy(input).ToListAsync();
            var roleListDtos = roles.Select(item =>
            {
                var dto = ObjectMapper.Map<RoleListDto>(item);
                dto.Users = ObjectMapper.Map<List<RoleUserDto>>(AsyncHelper.RunSync(() =>
                    UserManager.GetUsersInRoleAsync(item.Name)));
                return dto;
            }).ToList();
            return new PagedResultDto<RoleListDto>(roleCount, roleListDtos);
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Roles_Create,AppPermissions.Pages_Administration_Roles_Update)]
        public async Task<GetRoleForEditOutput> GetRoleForEdit(NullableIdDto input)
        {
            var permissions = PermissionManager.GetAllPermissions();
            var grantedPermissions = new Permission[0];
            RoleEditDto roleEditDto;

            var users = new List<RoleUserDto>();
            if (input.Id.HasValue)
            {
                var role = await RoleManager.GetRoleByIdAsync(input.Id.Value);
                grantedPermissions = (await RoleManager.GetGrantedPermissionsAsync(role)).ToArray();
                roleEditDto = ObjectMapper.Map<RoleEditDto>(role);
                users = ObjectMapper.Map<List<RoleUserDto>>(
                    await UserManager.GetUsersInRoleAsync(roleEditDto.DisplayName));
            }
            else
            {
                roleEditDto = new RoleEditDto();
            }

            return new GetRoleForEditOutput
            {
                Role = roleEditDto,
                Permissions = ObjectMapper.Map<List<FlatPermissionDto>>(permissions).OrderBy(p => p.DisplayName)
                    .ToList(),
                GrantedPermissionNames = grantedPermissions.Select(p => p.Name).ToList(),
                Users = users
            };
        }

        public async Task CreateOrUpdateRole(CreateOrUpdateRoleInput input)
        {
            if (input.Role.Id.HasValue)
            {
                await UpdateRoleAsync(input);
            }
            else
            {
                await CreateRoleAsync(input);
            }
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Roles_Delete)]
        public async Task DeleteRole(EntityDto input)
        {
            var role = await RoleManager.GetRoleByIdAsync(input.Id);
            var users = await UserManager.GetUsersInRoleAsync(role.Name);
            foreach (var user in users)
            {
                CheckErrors(await UserManager.RemoveFromRoleAsync(user, role.Name));
            }

            CheckErrors(await RoleManager.DeleteAsync(role));
        }

        [AbpAuthorize(AppPermissions.Pages_Administration_Roles_Update)]
        protected virtual async Task UpdateRoleAsync(CreateOrUpdateRoleInput input)
        {
            var role = await RoleManager.GetRoleByIdAsync(input.Role.Id.Value);
            role.DisplayName = input.Role.DisplayName;
            role.Description = input.Role.Description;
            role.Name = input.Role.DisplayName;
            role.IsDefault = input.Role.IsDefault;

            await UpdateMembersAsync(role, input.UserIds);
            await UpdateGrantedPermissionsAsync(role, input.GrantedPermissionNames);
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Roles_Create)]
        protected virtual async Task CreateRoleAsync(CreateOrUpdateRoleInput input)
        {
            var role = new Role(AbpSession.TenantId, input.Role.DisplayName)
            {
                IsDefault = input.Role.IsDefault,
                Description = input.Role.Description,
                Name = input.Role.DisplayName
            };
            CheckErrors(await RoleManager.CreateAsync(role));
            await CurrentUnitOfWork.SaveChangesAsync();
            await UpdateMembersAsync(role, input.UserIds);
            await UpdateGrantedPermissionsAsync(role, input.GrantedPermissionNames);
        }


        private async Task UpdateMembersAsync(Role role, long[] userIds)
        {
            var users1 = await UserManager.GetUsersInRoleAsync(role.Name);
            var users2 = new List<User>();
            foreach (var userId in userIds)
            {
                var user = await UserManager.FindByIdAsync(userId.ToString());
                if (user != null)
                {
                    users2.Add(user);
                }
            }

            var exceptUsers1 = users2.Except(users1);
            foreach (var user in exceptUsers1)
            {
                CheckErrors(await UserManager.AddToRoleAsync(user, role.Name));
            }

            var exceptUsers2 = users1.Except(users2);
            foreach (var user in exceptUsers2)
            {
                CheckErrors(await UserManager.RemoveFromRoleAsync(user, role.Name));
            }
        }

        private async Task UpdateGrantedPermissionsAsync(Role role, List<string> grantedPermissionNames)
        {
            if (role.IsStatic)
            {
                throw new UserFriendlyException("演示版不支持此功能，请选择其他角色测试");
            }

            var grantedPermissions = PermissionManager.GetPermissionsFromNamesByValidating(grantedPermissionNames);
            await RoleManager.SetGrantedPermissionsAsync(role, grantedPermissions);
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_Roles_Create,AppPermissions.Pages_Administration_Roles_Update)]
        public async Task<ValidateOutput> ValidateRole(ValidateInput<string, int> input)
        {

            if (input.Id == 0)
            {
                var one = await RoleManager.Roles.SingleOrDefaultAsync(r => r.Name == input.Value);
                if (one != null)
                {
                    return new ValidateOutput
                    {
                        Status = false,
                        Message = "角色名 " + input.Value + " 已被占用"
                    };
                }
                else
                {
                    return new ValidateOutput
                    {
                        Status = true,
                    };
                }
            }
            else
            {
                var one = await RoleManager.Roles.SingleOrDefaultAsync(r =>
                    r.Name == input.Value && r.Id != input.Id);
                if (one != null)
                {
                    return new ValidateOutput
                    {
                        Status = false,
                        Message = "角色名 " + input.Value + " 已被占用"
                    };
                }
                else
                {
                    return new ValidateOutput
                    {
                        Status = true,
                    };
                }
            }
        }
    }
}